local skynet = require "skynet"
local socketdriver = require "skynet.socketdriver"
local netpack = require "skynet.netpack"

local s = require "service"
local runconfig = require "runconfig"
local cjsonhelper = require "cjsonhelper"
local pbhelper = require "pbhelper"
local CMD = require "game_cmd"

local queue -- message queue

conns = {} -- [socket_id] = conn
players = {} -- [playerid] = gateplayer

-- 连接类
function conn()
    local m = {
        fd = nil,
        playerid = nil
    }
    return m
end

-- 玩家类
function gateplayer()
    local m = {
        playerid = nil,
        agent = nil,
        conn = nil
    }
    return m
end

local str_pack = function(cmd, msg)
    return table.concat(msg, ",") .. "\r\n"
end

local str_unpack = function(msgstr)
    -- local msg = cjsonhelper.json_unpack_no_prefix(msgstr)
    local msg, cmd_id = pbhelper.decode(msgstr)

    if not msg then
        return nil, {}
    end

    return msg.cmd, msg
end

s.resp.send_by_fd = function(source, fd, msg, cmd_id)
    if not conns[fd] then
        return
    end

    -- local szmsg = ""
    -- for i, v in pairs(msg) do
    --     szmsg = szmsg .. i .. " " .. v .. " "
    -- end
    -- skynet.error("send " .. fd .. " " .. szmsg, " cmd_id:", cmd_id)

    local pack = pbhelper.encodeAndPack(cmd_id, msg)
    if not pack then
        return
    end

    socketdriver.send(fd, pack)
end

s.resp.send = function(source, playerid, msg, cmd_id)
    local gplayer = players[playerid]
    if gplayer == nil then
        return
    end
    local c = gplayer.conn
    if c == nil then
        return
    end

    s.resp.send_by_fd(source, c.fd, msg, cmd_id)
end

s.resp.sure_agent = function(source, fd, playerid, agent)

    local conn = conns[fd]
    if not conn then -- 登陆过程中已经下线
        skynet.call("agentmgr", "lua", "reqkick", playerid, "未完成登陆即下线")
        return false
    end

    conn.playerid = playerid

    local gplayer = gateplayer()
    gplayer.playerid = playerid
    gplayer.agent = agent
    gplayer.conn = conn
    players[playerid] = gplayer

    return true
end

local disconnect = function(fd)
    local c = conns[fd]

    skynet.error("disconnect:", fd, c)
    if not c then
        return
    end

    local playerid = c.playerid
    skynet.error("disconnect11:", playerid)
    -- 还没完成登录
    if not playerid then
        return
        -- 已在游戏中
    else
        players[playerid] = nil
        local reason = "断线"
        skynet.error("disconnect2222:", reason)
        skynet.call("agentmgr", "lua", "reqkick", playerid, reason)
    end
end

s.resp.send_error_msg = function(source, fd, status, szMsg)
    skynet.error("error msg:", fd, status, szMsg)
    local erroRet = {
        cmd = -1,
        status = tonumber(status),
        msg = szMsg
    }
    s.resp.send_by_fd(skynet.self(), fd, erroRet, CMD.SERVER_COMMON_RET)
end

s.resp.kick = function(source, playerid)
    local gplayer = players[playerid]
    if not gplayer then
        return
    end

    local c = gplayer.conn
    players[playerid] = nil

    if not c then
        return
    end

    disconnect(c.fd)
    conns[c.fd] = nil
    socketdriver.close(c.fd)
end

s.resp.logout = function(source, playerid)
    local gplayer = players[playerid]
    if not gplayer then
        -- TODO: LOG
        return
    end

    players[playerid] = nil
    local c = gplayer.conn
    if not c then
        -- TODO: LOG
        return
    end
    c.playerid = nil

    skynet.call("agentmgr", "lua", "logout", playerid)
end

function process_connect(fd, addr)
    skynet.error("new conn fd:" .. fd .. " addr:" .. addr)
    local c = conn()
    conns[fd] = c
    c.fd = fd

    socketdriver.start(fd)
end

function process_close(fd)
    skynet.error("close fd:" .. fd)
    disconnect(fd)
    socketdriver.close(fd)
end

function process_error(fd, error)
    skynet.error("error fd:" .. fd .. " error:" .. error)
    disconnect(fd)
    socketdriver.close(fd)
end

function process_warning(fd, size)
    skynet.error("warning fd:" .. fd .. " size:" .. size)
end

function process_msg(fd, userdata, sz)
    local str = netpack.tostring(userdata, sz)
    local cmd, msg = str_unpack(str)
    if not cmd then
        s.resp.send_error_msg(_, fd, -1, "cmd错误")
        return
    end
    skynet.error("recv from fd:" .. fd .. " cmd:" .. cmd)

    local conn = conns[fd]
    if not conn then
        return
    end
    
    local playerid = conn.playerid

    if cmd == "login" then
        if playerid then
            s.resp.send_error_msg(_, fd, -1, "该连接已经登录账号,请退出后登录")
            return
        end
        local node = skynet.getenv("node")
        local nodecfg = runconfig[node]
        local loginid = math.random(1, #nodecfg.login)
        local login = "login" .. loginid
        skynet.send(login, "lua", "client", fd, cmd, msg)
        return
    elseif cmd == "register" then
        skynet.send("register", "lua", cmd, fd, msg)
        return
    end

    -- 尚未完成登录流程
    if not playerid then
        s.resp.send_error_msg(_, fd, -1, "还未登录")
        return
    else
        -- 完成登录流程
        local gplayer = players[playerid]
        local agent = gplayer.agent
        skynet.error("process_msg send agent:", agent, playerid, cmd, msg)
        skynet.send(agent, "lua", "client", cmd, msg)
    end
end

function process_more()
    for fd, msg, sz in netpack.pop, queue do
        skynet.fork(process_msg, fd, msg, sz)
    end
end

function socket_unpack(msg, sz)
    return netpack.filter(queue, msg, sz)
end

function socket_dispatch(_, _, q, type, ...)
    skynet.error("socket_dispatch type:" .. (type or "nil"))
    queue = q
    if type == "open" then
        process_connect(...)
    elseif type == "data" then
        process_msg(...)
    elseif type == "more" then
        process_more(...)
    elseif type == "close" then
        process_close(...)
    elseif type == "error" then
        process_error(...)
    elseif type == "warning" then
        process_warning(...)
    end
end

s.init = function()
    skynet.register_protocol({
        name = "socket",
        id = skynet.PTYPE_SOCKET,
        unpack = socket_unpack,
        dispatch = socket_dispatch
    })

    pbhelper.registerFile("./proto/export/lua/login.pb")
    pbhelper.registerFile("./proto/export/lua/common.pb")
    pbhelper.registerFile("./proto/export/lua/scene.pb")

    skynet.error("start gateway:", s.name, s.id)
    local node = skynet.getenv("node")
    local nodecfg = runconfig[node]
    local port = nodecfg.gateway[s.id].port

    local listenfd = socketdriver.listen("0.0.0.0", port)
    socketdriver.start(listenfd)
end

s.start(...)
